#include "voe_hardware.h"
#include "trace.h"
#include "critical_section_wrapper.h"


VoEHardwareImpl::VoEHardwareImpl(SharedData* shared) : _shared(shared) {
  WEBRTC_TRACE(kTraceMemory, kTraceVoice, VoEId(_shared->instance_id(), -1),
    "VoEHardwareImpl() - ctor");
}

VoEHardwareImpl::~VoEHardwareImpl() {
  WEBRTC_TRACE(kTraceMemory, kTraceVoice, VoEId(_shared->instance_id(), -1),
    "~VoEHardwareImpl() - dtor");
  
}

int VoEHardwareImpl::GetNumOfRecordingDevices(int& devices) {
  WEBRTC_TRACE(kTraceApiCall, kTraceVoice, VoEId(_shared->instance_id(), -1),
    "GetNumOfRecordingDevices(devices=?)");
  ANDROID_NOT_SUPPORTED(_shared->statistics());
  IPHONE_NOT_SUPPORTED(_shared->statistics());

  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }  

  devices = static_cast<int> (_shared->audio_device()->RecordingDevices());

  WEBRTC_TRACE(kTraceStateInfo, kTraceVoice,
    VoEId(_shared->instance_id(), -1), "  Output: devices=%d", devices);

  return 0;
}

int VoEHardwareImpl::GetNumOfPlayoutDevices(int& devices) {
  WEBRTC_TRACE(kTraceApiCall, kTraceVoice, VoEId(_shared->instance_id(), -1),
    "GetNumOfPlayoutDevices(devices=?)");
  ANDROID_NOT_SUPPORTED(_shared->statistics());
  IPHONE_NOT_SUPPORTED(_shared->statistics());

  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }

  devices = static_cast<int> (_shared->audio_device()->PlayoutDevices());

  WEBRTC_TRACE(kTraceStateInfo, kTraceVoice,
    VoEId(_shared->instance_id(), -1), "  Output: devices=%d", devices);

  return 0;
}

int VoEHardwareImpl::GetRecordingDeviceName(int index,
  char strNameUTF8[128], char strGuidUTF8[128]) {
  WEBRTC_TRACE(kTraceApiCall, kTraceVoice, VoEId(_shared->instance_id(), -1),
    "GetRecordingDeviceName(index=%d)", index);
  ANDROID_NOT_SUPPORTED(_shared->statistics());
  IPHONE_NOT_SUPPORTED(_shared->statistics());

  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }

  if (strNameUTF8 == NULL) {
    _shared->SetLastError(VE_INVALID_ARGUMENT, kTraceError,
      "GetRecordingDeviceName() invalid argument");
    return -1;
  }

  // Note that strGuidUTF8 is allowed to be NULL

  // Init len variable to length of supplied vectors
  const WebRtc_UWord16 strLen = 128;

  // Check if length has been changed in module
  assert(strLen == kAdmMaxDeviceNameSize);
  assert(strLen == kAdmMaxGuidSize);

  char name[strLen];
  char guid[strLen];

  // Get names from module
  if (_shared->audio_device()->RecordingDeviceName(index, name, guid) != 0) {
    _shared->SetLastError(VE_CANNOT_RETRIEVE_DEVICE_NAME, kTraceError,
      "GetRecordingDeviceName() failed to get device name");
    return -1;
  }

  // Copy to vectors supplied by user
  strncpy(strNameUTF8, name, strLen);
  WEBRTC_TRACE(kTraceStateInfo, kTraceVoice,
    VoEId(_shared->instance_id(), -1),
    "  Output: strNameUTF8=%s", strNameUTF8);

  if (strGuidUTF8 != NULL) {
    strncpy(strGuidUTF8, guid, strLen);
    WEBRTC_TRACE(kTraceStateInfo, kTraceVoice,
      VoEId(_shared->instance_id(), -1),
      "  Output: strGuidUTF8=%s", strGuidUTF8);
  }

  return 0;
}

int VoEHardwareImpl::GetPlayoutDeviceName(int index,
  char strNameUTF8[128], char strGuidUTF8[128]) {
  WEBRTC_TRACE(kTraceApiCall, kTraceVoice, VoEId(_shared->instance_id(), -1),
    "GetPlayoutDeviceName(index=%d)", index);
  ANDROID_NOT_SUPPORTED(_shared->statistics());
  IPHONE_NOT_SUPPORTED(_shared->statistics());

  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }

  if (strNameUTF8 == NULL) {
    _shared->SetLastError(VE_INVALID_ARGUMENT, kTraceError,
      "GetPlayoutDeviceName() invalid argument");
    return -1;
  }

  // Note that strGuidUTF8 is allowed to be NULL

  // Init len variable to length of supplied vectors
  const WebRtc_UWord16 strLen = 128;

  // Check if length has been changed in module
  assert(strLen == kAdmMaxDeviceNameSize);
  assert(strLen == kAdmMaxGuidSize);

  char name[strLen];
  char guid[strLen];

  // Get names from module
  if (_shared->audio_device()->PlayoutDeviceName(index, name, guid) != 0) {
    _shared->SetLastError(VE_CANNOT_RETRIEVE_DEVICE_NAME, kTraceError,
      "GetPlayoutDeviceName() failed to get device name");
    return -1;
  }

  // Copy to vectors supplied by user
  strncpy(strNameUTF8, name, strLen);
  WEBRTC_TRACE(kTraceStateInfo, kTraceVoice,
    VoEId(_shared->instance_id(), -1),
    "  Output: strNameUTF8=%s", strNameUTF8);

  if (strGuidUTF8 != NULL) {
    strncpy(strGuidUTF8, guid, strLen);
    WEBRTC_TRACE(kTraceStateInfo, kTraceVoice,
      VoEId(_shared->instance_id(), -1),
      "  Output: strGuidUTF8=%s", strGuidUTF8);
  }

  return 0;
}

int VoEHardwareImpl::SetRecordingDevice(int index,
  StereoChannel recordingChannel) {
  WEBRTC_TRACE(kTraceApiCall, kTraceVoice, VoEId(_shared->instance_id(), -1),
    "===================SetRecordingDevice(index=%d, recordingChannel=%d)=============",
    index, (int) recordingChannel);
  CriticalSectionScoped cs(_shared->crit_sec());
  IPHONE_NOT_SUPPORTED(_shared->statistics());
  // TODO(leozwang): Add this api to Android OpenSL ES implementation.

  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }

  bool isRecording(false);

  // Store state about activated recording to be able to restore it after the
  // recording device has been modified.
  if (_shared->audio_device()->Recording()) {
    WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_shared->instance_id(), -1),
      "SetRecordingDevice() device is modified while recording"
      " is active...");
    isRecording = true;
    if (_shared->audio_device()->StopRecording() == -1)
    {
      _shared->SetLastError(VE_AUDIO_DEVICE_MODULE_ERROR, kTraceError,
        "SetRecordingDevice() unable to stop recording");
      return -1;
    }
  }

  // We let the module do the index sanity

  // Set recording channel
  AudioDeviceModule::ChannelType recCh =
    AudioDeviceModule::kChannelBoth;
  switch (recordingChannel) {
  case kStereoLeft:
    recCh = AudioDeviceModule::kChannelLeft;
    break;
  case kStereoRight:
    recCh = AudioDeviceModule::kChannelRight;
    break;
  case kStereoBoth:
    // default setting kChannelBoth (<=> mono)
    break;
  }

  if (_shared->audio_device()->SetRecordingChannel(recCh) != 0) {
    _shared->SetLastError(VE_AUDIO_DEVICE_MODULE_ERROR, kTraceWarning,
      "SetRecordingChannel() unable to set the recording channel");
  }

  // Map indices to unsigned since underlying functions need that
  WebRtc_UWord16 indexU = static_cast<WebRtc_UWord16> (index);

  WebRtc_Word32 res(0);

  if (index == -1) {
    res = _shared->audio_device()->SetRecordingDevice(
      AudioDeviceModule::kDefaultDevice);
  } else if (index == -2) {
    res = _shared->audio_device()->SetRecordingDevice(
      AudioDeviceModule::kDefaultDevice);
  } else {
    res = _shared->audio_device()->SetRecordingDevice(indexU);
  }

  if (res != 0) {
    _shared->SetLastError(VE_AUDIO_DEVICE_MODULE_ERROR, kTraceError,
      "SetRecordingDevice() unable to set the recording device");
    return -1;
  }

  // Init microphone, so user can do volume settings etc
  if (_shared->audio_device()->InitMicrophone() == -1) {
    _shared->SetLastError(VE_CANNOT_ACCESS_MIC_VOL, kTraceWarning,
      "SetRecordingDevice() cannot access microphone");
  }

  // Set number of channels
  bool available = false;
  if (_shared->audio_device()->StereoRecordingIsAvailable(&available) != 0) {
    _shared->SetLastError(VE_SOUNDCARD_ERROR, kTraceWarning,
      "StereoRecordingIsAvailable() failed to query stereo recording");
  }

  if (_shared->audio_device()->SetStereoRecording(available) != 0) {
    _shared->SetLastError(VE_SOUNDCARD_ERROR, kTraceWarning,
      "SetRecordingDevice() failed to set mono recording mode");
  }

  // Restore recording if it was enabled already when calling this function.
  if (isRecording) {
	  WEBRTC_TRACE(kTraceInfo, kTraceVoice,  VoEId(_shared->instance_id(), -1), "Recording ok ...");

    if (!_shared->ext_recording()) {
      WEBRTC_TRACE(kTraceInfo, kTraceVoice,
        VoEId(_shared->instance_id(), -1),
        "SetRecordingDevice() recording is now being restored...");
      if (_shared->audio_device()->InitRecording() != 0) {
        WEBRTC_TRACE(kTraceError, kTraceVoice,
          VoEId(_shared->instance_id(), -1),
          "SetRecordingDevice() failed to initialize recording");
        return -1;
      }

      if (_shared->audio_device()->StartRecording() != 0) {
        WEBRTC_TRACE(kTraceError, kTraceVoice,
          VoEId(_shared->instance_id(), -1),
          "SetRecordingDevice() failed to start recording");
        return -1;
      }
    }
  }

  return 0;
}

int VoEHardwareImpl::InputDevice(int &index) {
  WEBRTC_TRACE(kTraceApiCall, kTraceVoice, VoEId(_shared->instance_id(), -1),
    "InputDevice");

  CriticalSectionScoped cs(_shared->crit_sec());

  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }
    
  if (_shared->audio_device()->InputDevice(&index))
    return -1;

  return 0;
}

int VoEHardwareImpl::SetPlayoutDevice(int index) {
  WEBRTC_TRACE(kTraceApiCall, kTraceVoice, VoEId(_shared->instance_id(), -1),
    "SetPlayoutDevice(index=%d)", index);
  CriticalSectionScoped cs(_shared->crit_sec());
  ANDROID_NOT_SUPPORTED(_shared->statistics());
  IPHONE_NOT_SUPPORTED(_shared->statistics());

  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }

  bool isPlaying(false);

  // Store state about activated playout to be able to restore it after the
  // playout device has been modified.
  if (_shared->audio_device()->Playing()) {
    WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_shared->instance_id(), -1),
      "SetPlayoutDevice() device is modified while playout is "
      "active...");
    isPlaying = true;
    if (_shared->audio_device()->StopPlayout() == -1) {
      _shared->SetLastError(VE_AUDIO_DEVICE_MODULE_ERROR, kTraceError,
        "SetPlayoutDevice() unable to stop playout");
      return -1;
    }
  }

  // We let the module do the index sanity

  // Map indices to unsigned since underlying functions need that
  WebRtc_UWord16 indexU = static_cast<WebRtc_UWord16> (index);

  WebRtc_Word32 res(0);

  if (index == -1) {
    res = _shared->audio_device()->SetPlayoutDevice(
      AudioDeviceModule::kDefaultDevice);
  } else if (index == -2) {
    res = _shared->audio_device()->SetPlayoutDevice(
      AudioDeviceModule::kDefaultDevice);
  } else {
    res = _shared->audio_device()->SetPlayoutDevice(indexU);
  }

  if (res != 0) {
    _shared->SetLastError(VE_SOUNDCARD_ERROR, kTraceError,
      "SetPlayoutDevice() unable to set the playout device");
    return -1;
  }

  // Init speaker, so user can do volume settings etc
  if (_shared->audio_device()->InitSpeaker() == -1) {
    _shared->SetLastError(VE_CANNOT_ACCESS_SPEAKER_VOL, kTraceWarning,
      "SetPlayoutDevice() cannot access speaker");
  }

  // Set number of channels
  bool available = false;
  _shared->audio_device()->StereoPlayoutIsAvailable(&available);
  if (_shared->audio_device()->SetStereoPlayout(available) != 0) {
    _shared->SetLastError(VE_SOUNDCARD_ERROR, kTraceWarning,
      "SetPlayoutDevice() failed to set stereo playout mode");
  }

  // Restore playout if it was enabled already when calling this function.
  if (isPlaying) {
    if (!_shared->ext_playout()) {
      WEBRTC_TRACE(kTraceInfo, kTraceVoice,
        VoEId(_shared->instance_id(), -1),
        "SetPlayoutDevice() playout is now being restored...");
      if (_shared->audio_device()->InitPlayout() != 0) { 
        WEBRTC_TRACE(kTraceError, kTraceVoice,
          VoEId(_shared->instance_id(), -1),
          "SetPlayoutDevice() failed to initialize playout");
        return -1;
      }
      if (_shared->audio_device()->StartPlayout() != 0) {
        WEBRTC_TRACE(kTraceError, kTraceVoice,
          VoEId(_shared->instance_id(), -1),
          "SetPlayoutDevice() failed to start playout");
        return -1;
      }
    }
  }

  return 0;
}

int VoEHardwareImpl::OutputDevice(int &index) {
  WEBRTC_TRACE(kTraceApiCall, kTraceVoice, VoEId(_shared->instance_id(), -1),
    "OutputDevice");

  CriticalSectionScoped cs(_shared->crit_sec());

  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }
    
  if (_shared->audio_device()->OutputDevice(&index))
    return -1;

  return 0;
}

int VoEHardwareImpl::GetRecordingDeviceStatus(bool& isAvailable) {
  WEBRTC_TRACE(kTraceApiCall, kTraceVoice, VoEId(_shared->instance_id(), -1),
    "GetRecordingDeviceStatus()");
  ANDROID_NOT_SUPPORTED(_shared->statistics());
  IPHONE_NOT_SUPPORTED(_shared->statistics());

  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }

  // We let the module do isRecording sanity

  bool available(false);

  // Check availability
  if (_shared->audio_device()->RecordingIsAvailable(&available) != 0) {
    _shared->SetLastError(VE_UNDEFINED_SC_REC_ERR, kTraceError,
      "  Audio Device error");
    return -1;
  }

  isAvailable = available;

  WEBRTC_TRACE(kTraceStateInfo, kTraceVoice,
    VoEId(_shared->instance_id(), -1),
    "  Output: isAvailable = %d)", (int) isAvailable);

  return 0;
}

int VoEHardwareImpl::GetPlayoutDeviceStatus(bool& isAvailable) {
  WEBRTC_TRACE(kTraceApiCall, kTraceVoice, VoEId(_shared->instance_id(), -1),
    "GetPlayoutDeviceStatus()");
  ANDROID_NOT_SUPPORTED(_shared->statistics());
  IPHONE_NOT_SUPPORTED(_shared->statistics());

  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }

  // We let the module do isPlaying sanity

  bool available(false);

  // Check availability
  if (_shared->audio_device()->PlayoutIsAvailable(&available) != 0) {
    _shared->SetLastError(VE_PLAY_UNDEFINED_SC_ERR, kTraceError,
      "  Audio Device error");
    return -1;
  }

  isAvailable = available;

  WEBRTC_TRACE(kTraceStateInfo, kTraceVoice,
    VoEId(_shared->instance_id(), -1),
    "  Output: isAvailable = %d)", (int) isAvailable);

  return 0;
}

